/*
 * Dumper.java   2008/07/28
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.common.lightweightlog.client;

import java.io.Serializable;
import java.util.Properties;

/**
 * <p>
 * This abstract class and its methods represent the Dumper, which is the wrapper
 *    of the stream used to unload the Entry.<br>
 *    The Dumper is created at Log startup and is builded with a custom set of properties
 *    prepared once by the Log class. Every Dumper is then stored into a proper implementation
 * of {@link Register}. </p>
 * <p>
 *  When extending Dumper for creating a custom one, the Dumper will have a global
 *  set of variable taken either by the property file or if not found from the {@link
 *  Constant} interface. Other custom set of properties will have this basic rule.
 *  Giving the dumper.properties with this declaration:</p>
 * <p>dumper.name=<i>mydumper</i>,<i>otherdumper</i></p>
 * <p>Then the following properties has to have root:</p>
 * <p>
 *  dumper.<i>mydumper</i>.property1=value<br>
 *  dumper.<i>mydumper</i>.property1=value<br>
 *  dumper.<i>otherdumper</i>.property3=value
 * </p>
 * <p>
 *  In this way property1, property2 will be pased to <i>mydumper</i> and property3
 *  to <i>otherdumper</i>. The two dumpers cannot share each other their set of properties.
 * </p>
 * <p>
 *  For investigating and retrieve a custom property the method get(String name)
 *  serve the purpose. Supposing we want to get property <i>severity</i> from <i>mydumper</i>
 * </p>
 * <p>
 *  <i>dumper.mydumper.severity=INFO</i><br/>
 *  is only called by:<br>
 *  <i>this.get("severity");</i><br/>
 *  The method will then construct internally kind of:<br/>
 *  <i>property.getProperty(PROP_ROOT + name + key);</i><br/>
 *  and return <i>INFO</i><br/>
 * </p>
 * <p>
 *  Following is a list of global Dumper variables and their explanation, see the
 *  {@link Constant} interface for their default value.</p>
 * <ul>
 *    <li><i>killonfailure</i> = if a DumperException is thrown during operation and
 *        this value is true then the Dumper.Status is set to FAILURE and the Dumper
 *        is invalid
 *    </li>
 *    <li><i>keepalive</i> = if true the dumper is always open</li>
 * </ul>
 * @author Leonardo Celati
 * @version 1.0
 */
public abstract class Dumper implements Constant, Serializable {
	
    /**
     * Possible status
     */
    public enum Status {
            STARTUP,
            RUNNING,
            SLEEPING,
            FAILURE,
    }
    /**
     * What happen on error...
     */
    private boolean killOnFailure;
    /**
     * The name of this Dumper
     */
    private String name;
    /**
     * Status for this Dumper
     */
    protected Status status;
    /**
     * Severity for this Dumper
     */
    private Severity severity;
    /**
     * Passed Properties for this Dumper
     */
    private Properties props;
    /**
     * Tell if we want to keep this Dumper always open
     */
    private boolean keepAlive;    
    /** 
     * See if we are open
     */
    protected boolean isOpen;
    
    /**
     * All the extended dumper are asked to call this constructor which will load the basic properties (severity and name are two of them).
     * @param p a custom set of properties
     */
    protected Dumper(Properties p) {
        assert(p!=null || p.isEmpty()): "Properties empty or null";
    	// startup...
    	this.status=Status.STARTUP;
        // store locally
    	this.props=p;

        // init our Dumper
        this.init();
        
        // ok...
        this.status = Status.RUNNING;
    }
    
    /**
     * Extended classes can override this method
     * to perform pre init operation
     */
    protected void preinit() {
    }
    
    /**
     * Init our Dumper, a call to preinit() and postinit() is also done
     */
    private void init() {
    	// we set the name then we start
        // setting the basic properties
        this.name = props.getProperty("name");        
        // perform preinit if required
        // extending classes can perform their
        // pre operation by overridding this method 
        this.preinit();

        try {
        	// severity for this Dumper, cannot have a default value
        	// that's why we throw error if we cannot parse the property
        	if (get("severity") != null)
                this.severity=Severity.valueOf( ((String)get("severity")).toUpperCase() );
        
        	// kill ?
        	if (get("killonfailure") != null)
                this.killOnFailure = Boolean.parseBoolean( get("killonfailure").toLowerCase() );
            else
             	this.killOnFailure = DEFAULT_KILL_ON_FAILURE;
            
            // keep alive ?
            if (get("keepalive") != null)
                this.keepAlive=Boolean.parseBoolean(get("keepalive").toLowerCase());
            else
            	this.keepAlive = Constant.DEFAULT_KEEP_ALIVE;

            // time to call post init...
            this.postinit();
            // opening...
            if (this.keepAlive) {
            	open();
            	this.isOpen=true;
            }
                   
        } catch (RuntimeException e) {            
            throw new ObjectInitializationException(e.getMessage());            
        } catch (Exception e) {
            throw new ObjectInitializationException(e.getMessage());
        }

    }
    
    /**
     * Extended classes can override this method
     * to perform post init operation
     */
    protected void postinit() {
    }

    /**
     * Facility for getting a given property by its key
     * @return The value of the property
     * @param key The key to look for
     */
    protected final String get(String key) {    
        //Log.serviceLog(PROP_ROOT  + name + "." + key);
        return props.getProperty(PROP_ROOT  + name + "." + key);
    }
    
    /**
     * Getter for status
     * @return Status
     */
    public final Status getStatus() {
    	return this.status;
    }
    
    /**
     * Getter for name
     * @return String
     */
    public final String getName() {
        return name;
    }

    /**
     * Getter for severity
     * @return Severity
     */
    public final boolean hasKillOnFailure() {
        return killOnFailure;
    }    

    /**
     * We want this always open
     * @return Severity
     */
    public final boolean isKeepAlive() {
        return this.keepAlive;
    }        
    
    /**
     * Getter for severity
     * @return Severity
     */
    public final Severity getSeverity() {
        return severity;
    }
    
    /**
     * True or false if this Dumper is open
     * @return true/false
     */
    public final boolean isOpen() {
    	return this.isOpen;
    }

    /**
     * Method called by the QueueDumper to prepare the Dumper itself, a call to open is also done.
     * Extending dumper must implement open method and put any operation in there.
     * @throws DumperException 
     * @throws it.wzm.common.lightweightlog.client.DumperException if something went wrong
     */
    final void prepare() throws DumperException {
        try {
        	// deciding if we are alive...
        	if (!this.keepAlive) {
        		// calling the abstract method
        		// we let user implement it...
        		open();
        		this.isOpen=true;
        	} else {
        		// se if it is the first time
        		// we open this Dumper
        		if (this.isOpen)
        			return;
        		else {
        			open();
        			this.isOpen=true;        			
        		}
        		
        	}       	
        	
        } catch (DumperException de) {
            // if we get here something went wrong
            // depends on flag we close the Dumper
            if (this.killOnFailure)
                this.status = Status.FAILURE;
            // then re throw the error...
            throw new DumperException(de);
        }
    }
    
    /**
     * Extending classes are asked to implement this method and put here all the operations.
     * @throws DumperException
     * @throws it.wzm.common.lightweightlog.client.DumperException if something went wrong
     */
    protected abstract void open() throws DumperException;
            
    /**
     * Unload the Entry to the stream, a call to postdump is also done.
     * @param e A passed object extending {@link Entry}
     * @throws DumperException if something went wrong
     */
    final <E extends Entry> void dump(E e) throws DumperException {
        assert(e!=null): "Cannot dump null";
        // just deciding if we need to unload this bean
        // by simply comparing to lg severity
        if (e.getSeverity().compareTo(this.getSeverity()) > -1) {
            try {
            	// check if we already open...
            	
            	
            	// now calling write...
                write(e);
            } catch (DumperException de) {
                if (this.killOnFailure) {
                    this.status=Status.FAILURE;
                }
                throw new DumperException(de);
            }
        }
    }

    /**
     * Write Entry to the preferred channel of the Dumper. 
     * Extending classes has to implement this method if they want to unload the Entry. 
     * @param e Anything extending Entry
     * @throws it.wzm.common.lightweightlog.client.DumperException if something went wrong
     */
    protected abstract <E extends Entry> void write(E e) throws DumperException;

    /**
     * Method called by the QueueDumper to postprepare the Dumper itself, a call to
     * post prepare is also made.
     * @throws DumperException 
     * @throws it.wzm.common.lightweightlog.client.DumperException if something went wrong
     */
    final void release() throws DumperException {
        try {
        	// deciding if we are alive...
        	if (!this.keepAlive)
        		// calling the abstract method
        		// we let user implement it...
        		close();
        	else 
        		return;
        } catch (DumperException de) {
            // if we get here something went wrong
            // depends on flag we close the Dumper
            if (this.killOnFailure)
                this.status = Status.FAILURE;
            // then re throw the error...
            throw new DumperException(de);
        }
    }    

    /**
     * Extending classes are asked to implements this method,
     * any close or finalize action will goes here.
     * which close the logger
     * @throws DumperException
     */
    protected abstract void close() throws DumperException;
    
    /**
     * Call this method to kill the Dumper
     */
    final void kill() {
    	try {
			close();
		} catch (DumperException e) {
			Log.serviceLog("Cannot close " + e.getMessage());			
		}
    }
    
}
